#pragma once


#include <iostream>
#include <string>
#include <memory>
#include <sys/select.h>
#include "Log.hpp"
#include "Socket.hpp"



using namespace SocketModule;
using namespace LogModule;

#define NUM sizeof(fd_set)*8
const int gdefaultfd = -1;



//最开始tcpserver只有一个listensockfe
class SelectServer
{
public:
    SelectServer(int port)
    :_port(port),
    _listen_socket(std::make_unique<TcpSocket>()),
    _isrunning(false)
    {

    }

    void Init()
    {
        _listen_socket->BuildTcpSocketMethod(_port);
        for(int i = 0;i < NUM ;i++)
        {
            _fd_array[i] = gdefaultfd;
        }
        //添加listensockfd到数组中
        _fd_array[0] = _listen_socket->Fd();
    }

    void Loop()
    {
        fd_set rfds;//读文件描述符集
        _isrunning = true;
        while(_isrunning)
        {
            //1.清空文件描述符集
            FD_ZERO(&rfds);
            struct timeval timeout = {10,0};//1秒内进行等待,超过1秒,就超时
            //2.将合法fd添加到rfds中
            int maxfd = gdefaultfd;
            for(int i=0;i<NUM;i++)
            {
                if(_fd_array[i] == gdefaultfd)
                {
                    continue;
                }
                FD_SET(_fd_array[i],&rfds);//包含listen套接字
                maxfd = max(maxfd,_fd_array[i]);
            }
            // FD_SET(_listen_socket->Fd(),&rfds);

            //我们不能让accept来阻塞检测新连接的到来,而应该让select来负责进行就绪事件的检测
            
            // int n = select(_listen_socket->Fd() + 1,&rfds,nullptr,nullptr,nullptr);//阻塞
            int n = select(maxfd + 1,&rfds,nullptr,nullptr,&timeout);//通知上层的任务
            //读取rdfs上的就绪事件之后,我们要一直把他提取出来,不然一直存在
            switch(n)
            {
                case 0:
                    std::cout << "time out..." << std::endl;
                    break;
                case -1:
                    perror("select");
                    break;
                default:
                    //rfds: 内核告诉用户,你关心的rfds中的fd,有哪些已经就绪了
                    std::cout << "有事件就绪了... , timeout: " << timeout.tv_sec << ":" << timeout.tv_usec << std::endl;
                    //处理新连接
                    Dispatcher(rfds);//把已经就绪的事件派发给不同的模块
                    break;
            }

            // InetAddr client;
            // int newsockfd = _listen_socket->Accepter(&client);
            //根据listen套接字,检测有没有新的链接
            //accept: 阻塞的
            //从listensockfd获取新链接,本质也是一种IO
            // 我们这种IO,input -> listensockfd只会关系读事件就绪
            //IO = 等 + 拷贝,所以Accepter不能用
            //要先把listensockfd添加到select内部

        }
        _isrunning = false;
    }


    void Accepter()
    {
        //listenscokfd就绪了! 获取新链接不就好了吗?
        InetAddr client;
        int newfd = _listen_socket->Accepter(&client);//这里accepter时就不会阻塞了,select已经告诉我了,只拷贝就行了
        if (newfd < 0)
        {
            return;
        }
        else
        {
            std::cout << "获得了一个新链接: " << newfd << " client info: " << client.Addr() << std::endl;
            // recv() ?? send() ?? 不能直接读,客户端可能只连上了,还没有进行发送数据
            // 如果读了,就又会陷入等待中
            // 所以,newfd也要托管给select,让select帮我进行关心新的sockfd上面的读事件就绪
            // 怎么做?
            // 怎么把新的newfd托管给select?让select帮我去关心newfd上面的读事件呢?把newfd,添加到辅助数组即可
            int pos = -1;
            for(int j = 0;j < NUM ;j++)
            {
                if(_fd_array[j] == gdefaultfd)
                {
                    pos = j;
                    break;
                }
            }
            if(pos == -1)
            {
                LOG(LogLevel::ERROR) << "服务器已经满了....";
                close(newfd);
            }
            else
            {
                _fd_array[pos] = newfd;
            }

        }
    }
    void Recver(int i)
    {
        //合法的就绪的,普通的fd
        //要根据协议进行读取
        char buffer[1024];
        ssize_t n = recv(_fd_array[i],buffer,sizeof(buffer)-1,0);//这里的recv不会阻塞,select已经告诉我们他已经就绪了
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "client# " << buffer << std::endl;
            std::string message = "echo# ";
            message += buffer;
            send(_fd_array[i],message.c_str(),message.size(),0);
        }
        else if(n == 0)
        {
            LOG(LogLevel::DEBUG) << "客户端退出, sockfd: "<<_fd_array[i];
            close(_fd_array[i]);
            _fd_array[i] = gdefaultfd;
        }
        else
        {
            LOG(LogLevel::DEBUG) << "客户端读取出错, sockfd: "<<_fd_array[i];
            close(_fd_array[i]);
            _fd_array[i] = gdefaultfd;
        }
    }

    void Dispatcher(fd_set& rfds)//rfds就可能会有更多的fd就绪了,就不仅仅是listenfd就绪了
    {
        for(int i=0;i<NUM;i++)
        {
            if(_fd_array[i]==gdefaultfd)
            {
                continue;
            }
            //文件描述符,先得是合法得

            //区分是IO读还是获取新链接
            if(_fd_array[i] == _listen_socket->Fd())
            {
                if(FD_ISSET(_fd_array[i],&rfds))
                {
                    Accepter();//获取新链接
                }
            }
            else
            {
                if(FD_ISSET(_fd_array[i],&rfds))
                {
                    Recver(i);//IO处理
                }
            }
            
        }
        
    }

    ~SelectServer()
    {

    }
private:
    uint16_t _port;
    std::unique_ptr<Socket> _listen_socket;
    bool _isrunning;
    int _fd_array[NUM]; //辅助数组,帮助我们进行设置关心的文件描述符
};



